package sh.grapendaal.tsuushin.module.uno.engine.service.impl;

import java.io.File;
import java.io.Serializable;
import java.util.Set;

import org.threeten.bp.LocalDateTime;
import org.threeten.bp.format.DateTimeFormatter;

import sh.grapendaal.tsuushin.module.uno.engine.domain.Player;
import sh.grapendaal.tsuushin.module.uno.engine.domain.Statistics;
import sh.grapendaal.tsuushin.module.uno.engine.domain.achievement.Achievement;
import sh.grapendaal.tsuushin.module.uno.engine.dto.GameReport;
import sh.grapendaal.tsuushin.module.uno.engine.service.PersistenceService;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

public class PersistenceServiceImpl implements PersistenceService {

	private static final DateTimeFormatter GAME_REPORT_FILENAME_FORMAT = DateTimeFormatter
			.ofPattern("yyyy-MM-dd-HH-mm-ss");
	private static final String PLAYER_FILE_EXT = ".upl";
	private static final String GAME_REPORT_DIRECTORY = "report";
	private static final String GAME_REPORT_EXT = ".uno";

	private File dataDirectory;
	private ObjectMapper mapper;

	public PersistenceServiceImpl(File dataDirectory) {
		if (!dataDirectory.exists()) {
			dataDirectory.mkdirs();
		}
		File gameReportDir = new File(dataDirectory, GAME_REPORT_DIRECTORY);
		if (!gameReportDir.exists()) {
			gameReportDir.mkdirs();
		}

		this.dataDirectory = dataDirectory;
		this.mapper = new ObjectMapper();
		this.mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
	}

	@Override
	public Player loadPlayer(String nickname) {
		File playerFile = getFileForPlayer(nickname);
		if (playerFile.exists()) {
			PersistedPlayer persistedPlayer = readValue(playerFile, PersistedPlayer.class);
			return new Player(persistedPlayer.getNickname(), persistedPlayer.getStatistics(),
					persistedPlayer.getUnlockedAchievements());
		}
		return null;
	}

	@Override
	public void storePlayer(Player player) {
		File playerFile = getFileForPlayer(player.getNickname());
		PersistedPlayer persistedPlayer = this.mapper.convertValue(player, PersistedPlayer.class);
		writeValue(playerFile, persistedPlayer);
	}

	@Override
	public void storeGameReport(GameReport gameReport) {
		String filename = GAME_REPORT_FILENAME_FORMAT.format(LocalDateTime.now()) + GAME_REPORT_EXT;
		writeValue(new File(new File(this.dataDirectory, GAME_REPORT_DIRECTORY), filename), gameReport);
	}

	private File getFileForPlayer(String nickname) {
		return new File(this.dataDirectory, nickname + PLAYER_FILE_EXT);
	}

	private <T> T readValue(File file, Class<T> type) {
		try {
			return this.mapper.readValue(file, type);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	private void writeValue(File file, Object value) {
		try {
			this.mapper.writeValue(file, value);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * This class is used to filter out data we don't want to persist from the
	 * actual Player class.
	 */
	@SuppressWarnings("unused")
	private static class PersistedPlayer implements Serializable {

		private static final long serialVersionUID = 6420379292012060382L;

		private String nickname;
		private Statistics statistics;
		private Set<Class<? extends Achievement>> unlockedAchievements;

		public String getNickname() {
			return nickname;
		}

		public void setNickname(String nickname) {
			this.nickname = nickname;
		}

		public Statistics getStatistics() {
			return statistics;
		}

		public void setStatistics(Statistics statistics) {
			this.statistics = statistics;
		}

		public Set<Class<? extends Achievement>> getUnlockedAchievements() {
			return unlockedAchievements;
		}

		public void setUnlockedAchievements(Set<Class<? extends Achievement>> unlockedAchievements) {
			this.unlockedAchievements = unlockedAchievements;
		}

	}

}
